app.factory('dataRequest', ['$http', function dataRequestFactory($http) {
  // Cache config
  var config;

  function getDataConfig() {
    var def = jQuery.Deferred();

    if (config) return def.resolve().promise();
    $http.get('/config/parse')
      .success(function(data) {
        config = data;
        def.resolve();
      })
      .error(function(data, status) {
        throw new Error('Cannot get REST configuration. Status: ' + status);
      });

    return def.promise();
  }

  function baseRequest(options) {
    var def = jQuery.Deferred();
    if (!options.url) def.reject(TypeError('url is not provided'));
    // Adding rest headers to the request options.
    // If headers provided, extend the headers object.
    options.headers = options.headers ? jQuery.extend(options.headers, config.restKeys) : config.restKeys;
    $http(options)
      .success(function(data, status) {
        def.resolve(data);
      })
      .error(function(data, status) {
        def.reject(data, status);
      });

    return def.promise();
  }

  // Public interface
  function create(className, id, values, session) {
    var def = jQuery.Deferred();

    if (!config) {
      getDataConfig().then(send);
    } else {
      send();
    }

    function send() {
      var url = config.restObj.create.url
        .replace('<class>', className)
        .replace('<id>', id);
      var options = {
        method: config.restObj.create.method,
        url: url,
        data: values,
        headers: {
          "X-Parse-Session-Token": session
        }
      };

      baseRequest(options)
        .then(function(data) {
          def.resolve(data);
        }, function(err, status) {
          def.reject(err, status);
        });
    }

    return def.promise();
  }

  /*
  |--------------------------------------------------------------------------
  | function: read(className, id, limit)
  |--------------------------------------------------------------------------
  | params: 
  |   className: the class name in database.
  |   id: id of object in the provided class name data
  |       if id is null, then all object of this class name are resolved.
  |   limit: the limit of objects returned. 
  |
  | returns:
  |   promise: see the Promise Interface below.
  |--------------------------------------------------------------------------
  */
  function read(className, id, limit) {
    var def = jQuery.Deferred();

    if (!config) {
      getDataConfig().then(send);
    } else {
      send();
    }

    function send() {
      var url = config.restObj.read.url
        .replace('<class>', className)
        .replace('<id>', id || '');
      var options = {
        method: config.restObj.read.method,
        url: url,
      };
      if (limit) options.params = {limit: limit};

      baseRequest(options)
        .then(function(data) {
          def.resolve(data);
        }, function(err) {
          def.reject(err, status);
        });
    }

    return def.promise();
  }

  function update(className, id, values, session) {
    var def = jQuery.Deferred();

    if (!config) {
      getDataConfig().then(send);
    } else {
      send();
    }

    function send() {
      var url = config.restObj.update.url
        .replace('<class>', className)
        .replace('<id>', id);
      var options = {
        method: config.restObj.update.method,
        url: url,
        data: values,
        headers: {
          "X-Parse-Session-Token": session
        }
      };

      baseRequest(options)
        .then(function(data) {
          def.resolve(data);
        }, function(err, status) {
          def.reject(err, status);
        });
    }

    return def.promise();
  }

  function del(className, id, session) {
    if (!id) throw new Error('Cannot delete object without an id');

    var def = jQuery.Deferred();

    if (!config) {
      getDataConfig().then(send);
    } else {
      send();
    }

    function send() {
      var url = config.restObj.delete.url
        .replace('<class>', className)
        .replace('<id>', id);
      var options = {
        method: config.restObj.delete.method,
        url: url,
        headers: {
          "X-Parse-Session-Token": session
        }
      };

      baseRequest(options)
        .then(function(data) {
          def.resolve(data);
        }, function(err, status) {
          def.reject(err, status);
        });
    }

    return def.promise();
  }

  /*
  |------------------------------
  | Promise interface
  |------------------------------
  | Functions:
  |   create, read, update, delete
  | Return values:
  |   jQuery.Deferred().promise()
  | Promise interface:
  |   resolve(data)
  |   reject(httpError, statusCode)
  |------------------------------
  */

  return {
    create: create,
    read: read,
    update: update,
    delete: del
  };
}]);
